package com.obggtc.picture.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁工具类
 */
@Component
public class RedisLockUtils {

    private static final Logger log = LoggerFactory.getLogger(RedisLockUtils.class);
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 锁的前缀
    private static final String LOCK_PREFIX = "lock:";
    
    // 默认锁过期时间（秒）
    private static final long DEFAULT_EXPIRE_TIME = 30;
    
    // 获取锁的超时时间（毫秒）
    private static final long DEFAULT_TIMEOUT = 5000;

    // Lua脚本：释放锁
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";

    // Lua脚本：续期锁
    private static final String RENEW_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('expire', KEYS[1], ARGV[2]) " +
        "else " +
        "    return 0 " +
        "end";

    /**
     * 获取锁
     * @param lockKey 锁的键
     * @return 锁的值（用于释放锁）
     */
    public String tryLock(String lockKey) {
        return tryLock(lockKey, DEFAULT_EXPIRE_TIME, DEFAULT_TIMEOUT);
    }

    /**
     * 获取锁
     * @param lockKey 锁的键
     * @param expireTime 锁的过期时间（秒）
     * @return 锁的值（用于释放锁）
     */
    public String tryLock(String lockKey, long expireTime) {
        return tryLock(lockKey, expireTime, DEFAULT_TIMEOUT);
    }

    /**
     * 获取锁
     * @param lockKey 锁的键
     * @param expireTime 锁的过期时间（秒）
     * @param timeout 获取锁的超时时间（毫秒）
     * @return 锁的值（用于释放锁），null表示获取失败
     */
    public String tryLock(String lockKey, long expireTime, long timeout) {
        String key = LOCK_PREFIX + lockKey;
        String value = UUID.randomUUID().toString();
        
        long start = System.currentTimeMillis();
        
        while (System.currentTimeMillis() - start < timeout) {
            // 尝试获取锁
            Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(success)) {
                return value;
            }
            
            // 短暂休眠后重试
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        
        return null;
    }

    /**
     * 立即尝试获取锁，不等待
     * @param lockKey 锁的键
     * @param expireTime 锁的过期时间（秒）
     * @return 锁的值（用于释放锁），null表示获取失败
     */
    public String tryLockImmediate(String lockKey, long expireTime) {
        String key = LOCK_PREFIX + lockKey;
        String value = UUID.randomUUID().toString();
        
        Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
        
        return Boolean.TRUE.equals(success) ? value : null;
    }

    /**
     * 释放锁
     * @param lockKey 锁的键
     * @param lockValue 锁的值
     * @return 释放成功返回true，失败返回false
     */
    public boolean unlock(String lockKey, String lockValue) {
        String key = LOCK_PREFIX + lockKey;
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(script, Collections.singletonList(key), lockValue);
        
        return Long.valueOf(1).equals(result);
    }

    /**
     * 续期锁
     * @param lockKey 锁的键
     * @param lockValue 锁的值
     * @param expireTime 新的过期时间（秒）
     * @return 续期成功返回true，失败返回false
     */
    public boolean renewLock(String lockKey, String lockValue, long expireTime) {
        String key = LOCK_PREFIX + lockKey;
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(RENEW_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(script, Collections.singletonList(key), lockValue, String.valueOf(expireTime));
        
        return Long.valueOf(1).equals(result);
    }

    /**
     * 检查锁是否存在
     * @param lockKey 锁的键
     * @return 锁是否存在
     */
    public boolean isLocked(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 强制释放锁（管理员功能）
     * @param lockKey 锁的键
     * @return 释放成功返回true，失败返回false
     */
    public boolean forceUnlock(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    /**
     * 获取锁的剩余过期时间
     * @param lockKey 锁的键
     * @return 剩余过期时间（秒），-1表示永不过期，-2表示键不存在
     */
    public long getLockTTL(String lockKey) {
        String key = LOCK_PREFIX + lockKey;
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 使用锁执行业务逻辑
     * @param lockKey 锁的键
     * @param task 要执行的任务
     * @return 执行结果
     */
    public <T> T executeWithLock(String lockKey, LockTask<T> task) {
        return executeWithLock(lockKey, DEFAULT_EXPIRE_TIME, DEFAULT_TIMEOUT, task);
    }

    /**
     * 使用锁执行业务逻辑
     * @param lockKey 锁的键
     * @param expireTime 锁的过期时间（秒）
     * @param timeout 获取锁的超时时间（毫秒）
     * @param task 要执行的任务
     * @return 执行结果
     */
    public <T> T executeWithLock(String lockKey, long expireTime, long timeout, LockTask<T> task) {
        String lockValue = tryLock(lockKey, expireTime, timeout);
        
        if (lockValue == null) {
            throw new RuntimeException("Failed to acquire lock: " + lockKey);
        }
        
        try {
            return task.execute();
        } catch (Exception e) {
            log.error("使用锁执行业务逻辑调用失败",e);
            return null;
        } finally {
            unlock(lockKey, lockValue);
        }
    }

    /**
     * 使用锁执行业务逻辑（无返回值）
     * @param lockKey 锁的键
     * @param task 要执行的任务
     */
    public void executeWithLock(String lockKey, Runnable task) {
        executeWithLock(lockKey, DEFAULT_EXPIRE_TIME, DEFAULT_TIMEOUT, task);
    }

    /**
     * 使用锁执行业务逻辑（无返回值）
     * @param lockKey 锁的键
     * @param expireTime 锁的过期时间（秒）
     * @param timeout 获取锁的超时时间（毫秒）
     * @param task 要执行的任务
     */
    public void executeWithLock(String lockKey, long expireTime, long timeout, Runnable task) {
        String lockValue = tryLock(lockKey, expireTime, timeout);
        
        if (lockValue == null) {
            throw new RuntimeException("Failed to acquire lock: " + lockKey);
        }
        
        try {
            task.run();
        } finally {
            unlock(lockKey, lockValue);
        }
    }

    /**
     * 锁任务接口
     * @param <T> 返回值类型
     */
    @FunctionalInterface
    public interface LockTask<T> {
        T execute() throws Exception;
    }

    /**
     * 文件上传锁
     * @param filename 文件名
     * @return 锁的值
     */
    public String tryFileUploadLock(String filename) {
        return tryLock("file:upload:" + filename, 60, 3000); // 1分钟过期，3秒超时
    }

    /**
     * 用户操作锁
     * @param username 用户名
     * @param operation 操作类型
     * @return 锁的值
     */
    public String tryUserOperationLock(String username, String operation) {
        return tryLock("user:operation:" + username + ":" + operation, 30, 2000); // 30秒过期，2秒超时
    }

    /**
     * 图片处理锁
     * @param imageId 图片ID
     * @return 锁的值
     */
    public String tryImageProcessLock(String imageId) {
        return tryLock("image:process:" + imageId, 120, 5000); // 2分钟过期，5秒超时
    }

    /**
     * 视频上传锁
     * @param filename 视频文件名
     * @return 锁的值
     */
    public String tryVideoUploadLock(String filename) {
        return tryLock("video:upload:" + filename, 300, 10000); // 5分钟过期，10秒超时（视频上传时间较长）
    }

    /**
     * 视频处理锁
     * @param videoId 视频ID
     * @return 锁的值
     */
    public String tryVideoProcessLock(String videoId) {
        return tryLock("video:process:" + videoId, 600, 10000); // 10分钟过期，10秒超时（视频处理时间更长）
    }
} 